/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.inspur.edp.metadata.rtcustomization.servermanager.deploymanager;

import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.lcm.metadata.api.entity.MetadataDto;
import com.inspur.edp.lcm.metadata.common.MetadataDtoConverter;
import com.inspur.edp.metadata.rtcustomization.api.AbstractCustomizedContent;
import com.inspur.edp.metadata.rtcustomization.api.entity.DimensionExtendEntity;
import com.inspur.edp.metadata.rtcustomization.api.entity.GspMdChangeset;
import com.inspur.edp.metadata.rtcustomization.api.entity.GspMdExtRelation;
import com.inspur.edp.metadata.rtcustomization.serverapi.CustomizationServerService;
import com.inspur.edp.metadata.rtcustomization.servermanager.entity.MetadataSyncInfo;
import com.inspur.edp.metadata.rtcustomization.servermanager.event.MetadataDeployEventBroker;
import com.inspur.edp.metadata.rtcustomization.servermanager.extend.DeployStrategy;
import com.inspur.edp.metadata.rtcustomization.servermanager.utils.MetadataDeployUtils;
import com.inspur.edp.metadata.rtcustomization.spi.CustomizationExtHandler;
import com.inspur.edp.metadata.rtcustomization.spi.CustomizationSerializer;
import com.inspur.edp.metadata.rtcustomization.spi.event.ExtMdSavedArgs;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

/**
 * @author zhaoleitr
 */
@Slf4j
public class MdRtCustomizationDeployManager {

    private static MdRtCustomizationDeployManager instance;
    private final Map<String, GspMetadata> metadataMap = new HashMap<>();
    private final CustomizationServerService customizationServerService;
    private Map<String, AbstractCustomizedContent> changeMap = new HashMap<>();

    private MdRtCustomizationDeployManager() {
        customizationServerService = SpringBeanUtils.getBean(CustomizationServerService.class);
    }

    public static MdRtCustomizationDeployManager getInstance() {
        if (instance == null) {
            instance = new MdRtCustomizationDeployManager();
        }
        return instance;
    }

    public void deployCustomizationMdList(List<GspMetadata> metadataList, List<GspMdExtRelation> extRelations, List<GspMdChangeset> changeSets) {
        List<MetadataSyncInfo> infoList = new ArrayList<>();
        for (GspMetadata metadata : metadataList) {
            List<DimensionExtendEntity> extendEntitys = customizationServerService.getMetadataInfoListRecusively(metadata.getHeader().getId(), metadata.getHeader().getCertId());
            MetadataDto rootMetadataDto = customizationServerService.getBasicMetadataByExtMdId(metadata.getHeader().getId(), metadata.getHeader().getCertId());
            GspMetadata rootMetadata = MetadataDtoConverter.asMetadata(rootMetadataDto);
            //构造向下同步实体，同时检查数据完整性
            MetadataSyncInfo info = getSyncInfo(metadata, extRelations, changeSets, extendEntitys, rootMetadata);
            infoList.add(info);
            for (DimensionExtendEntity extendEntity : extendEntitys) {
                if (extendEntity.getBasicMetadataId().equals(metadata.getHeader().getId())) {
                    GspMetadata extendMetadata = customizationServerService.getMetadata(extendEntity.getExtendMetadataEntity().getHeader().getId());
                    GspMetadata oldMetadata = customizationServerService.getMetadata(metadata.getHeader().getId());
                    AbstractCustomizedContent parentChange = DeployStrategy.getExtContent4SameLevel(metadata, oldMetadata, rootMetadata);
                    //运行时定制元数据冲突检查
                    checkConflict(metadata, extendMetadata, parentChange, extendEntitys, rootMetadata);
                }
            }
        }
        //同步保存
        syncMetadataInfos(infoList);
        clearSyncInfo();
    }

    private MetadataSyncInfo getSyncInfo(GspMetadata metadata, List<GspMdExtRelation> extRelations, List<GspMdChangeset> changeSets, List<DimensionExtendEntity> extendEntities, GspMetadata rootMetadata) {
        MetadataSyncInfo info = new MetadataSyncInfo(metadata);
        GspMdExtRelation extRelation = extRelations.stream().filter(item -> item.getExtMdId().equals(metadata.getHeader().getId())).findAny().orElse(null);
        if (extRelation == null) {
            extRelation = customizationServerService.getExtRelationByExtMdIdAndCertId(metadata.getHeader().getId(), metadata.getHeader().getCertId());
            if (extRelation == null) {
                throw new RuntimeException("运行时定制元数据扩展关系不存在");
            }
        }
        info.setExtRelation(extRelation);

        GspMdChangeset changeSet = changeSets.stream().filter(item -> item.getMetadataId().equals(metadata.getHeader().getId())).findAny().orElse(null);
        if (changeSet != null) {
            info.setChangeSet(changeSet);
        }

        //如果目标环境中有元数据，检查元数据是否被扩展，如果被扩展了，检查冲突
        if (CollectionUtils.isEmpty(extendEntities)) {
            return info;
        }

        info.setNeedSync(true);
        info.setDimensionExtendEntityList(extendEntities);
        info.setRootMetadata(rootMetadata);
        return info;
    }


    private void syncMetadataInfos(List<MetadataSyncInfo> infoList) {
        for (MetadataSyncInfo info : infoList) {
            GspMetadata metadata = info.getMetadata();
            GspMetadata preversionMetadata = customizationServerService.getMetadata(metadata.getHeader().getId());
            if (preversionMetadata != null) {
                metadata.setVersion(String.valueOf(Integer.parseInt(preversionMetadata.getVersion()) + 1));
                metadata.setPreviousVersion(preversionMetadata.getVersion());
            } else {
                //没有前一个版本，则版本为1
                metadata.setVersion("1");
            }

            //保存扩展关系
            saveExtRelation(info, metadata);

            //保存当前元数据
            customizationServerService.saveExtMetadata(metadata);

            //运行时定制元数据保存后事件

            fireExtMdSavedEvent(metadata, info.getExtRelation(), null);

            //启用到运行时
            customizationServerService.releaseMetadataToRt(metadata.getHeader().getId(), metadata.getHeader().getCertId());

            //保存变更集
            saveChangeSet(info, metadata, preversionMetadata);

            //同步
            if (info.isNeedSync()) {
                syncMetadata(info.getMetadata(), info.getDimensionExtendEntityList(), MetadataDeployUtils.getCustomizationExtHandler(info.getMetadata().getHeader().getType()), info.getRootMetadata());
            }
        }
    }

    public void checkConflict(GspMetadata basicMetadata, GspMetadata metadata, AbstractCustomizedContent parentChange, List<DimensionExtendEntity> extendEntitys, GspMetadata rootMetadata) {
        //当元数据修改增量为空时，不需要进行合并处理
        if (Objects.isNull(parentChange)) {
            return;
        }
        AbstractCustomizedContent changeToParent = null;
        GspMdChangeset mdChangeset = customizationServerService.getChangeset(metadata.getHeader().getId(), metadata.getHeader().getCertId());
        CustomizationSerializer changeSetSerializerManager = MetadataDeployUtils.getChangeSetSerializer(metadata.getHeader().getType());
        if (mdChangeset != null) {
            changeToParent = (AbstractCustomizedContent) changeSetSerializerManager.deSerialize(mdChangeset.getContent());
            DeployStrategy.checkMergeConflict(basicMetadata.getHeader().getType(), parentChange, changeToParent, rootMetadata);
        }

        AbstractCustomizedContent newParentChange;
        if (changeToParent == null) {
            newParentChange = parentChange;
        } else {
            newParentChange = DeployStrategy.changeMerge(metadata.getHeader().getType(), parentChange, changeToParent, rootMetadata, basicMetadata, metadata);
        }
        //将合并的增量记录下来，合并时使用
        if (newParentChange == null) {
            return;
        }
        AbstractCustomizedContent clonedNewParentChange = CloneNewParentChange(newParentChange, changeSetSerializerManager);
        //because of extendmetadata has its own changeset.
        // if basicmetadata has many extendmetadatas, the changes must be seperated by extendmetadata id ,not with the same basicmetadata id
        changeMap.put(metadata.getHeader().getId(), clonedNewParentChange);
        metadataMap.put(metadata.getHeader().getId(), metadata);
        //find the new extendmetadata, the metatadata id become parent id
        //find all extendmetadatas, if exist, checkConflict. if not exist,finish.
        for (DimensionExtendEntity entity : extendEntitys) {
            if (entity.getBasicMetadataId().equals(metadata.getHeader().getId())) {
                GspMetadata extendMetadata = customizationServerService.getMetadata(entity.getExtendMetadataEntity().getHeader().getId());
                //exit condition is extend relation not exits
                //从扩展链中找到当前元数据的扩展元数据，递归向下检查冲突
                checkConflict(metadata, extendMetadata, newParentChange, extendEntitys, rootMetadata);
            }

        }

    }

    private AbstractCustomizedContent CloneNewParentChange(AbstractCustomizedContent change, CustomizationSerializer changeSetSerializerManager) {
        String contentStr = changeSetSerializerManager.serialize(change);
        return (AbstractCustomizedContent) changeSetSerializerManager.deSerialize(contentStr);
    }

    public void syncMetadata(GspMetadata metadata, List<DimensionExtendEntity> dimensionExtendEntityList, CustomizationExtHandler manager, GspMetadata rootMetadata) {
        if (Objects.nonNull(manager)) {
            for (DimensionExtendEntity entity : dimensionExtendEntityList) {
                if (!entity.getBasicMetadataId().equals(metadata.getHeader().getId())) {
                    continue;
                }
                //find extend metadata and mergedChange
                AbstractCustomizedContent mergedChange = changeMap.get(entity.getExtendMetadataEntity().getHeader().getId());
                //如果获取增量为空，说明不需要合并，继续查找需要合并的元数据。
                if (Objects.isNull(mergedChange)) {
                    continue;
                }
                GspMetadata extMetadata = metadataMap.get(entity.getExtendMetadataEntity().getHeader().getId());
                if (Objects.isNull(extMetadata)) {
                    log.info(String.format("存在扩展关系，但扩展元数据内容为空。扩展关系为：%s [type:%s id:%s]扩展了%s [id:%s]", entity.getExtendMetadataEntity().getHeader().getCode(), entity.getExtendMetadataEntity().getHeader().getType(), entity.getExtendMetadataEntity().getHeader().getId(), entity.getBasicMetadataCode(), entity.getBasicMetadataId()));
                    continue;
                }
                GspMetadata mergedMetadata = DeployStrategy.mergeMetadata(manager, rootMetadata, metadataMap.get(entity.getExtendMetadataEntity().getHeader().getId()), mergedChange);

                int version = Integer.parseInt(mergedMetadata.getVersion());
                String newVersion = String.valueOf(version + 1);
                mergedMetadata.setPreviousVersion(String.valueOf(version));
                mergedMetadata.setVersion(newVersion);

                GspMdExtRelation extRelation = customizationServerService.getExtRelationByExtMdIdAndCertId(mergedMetadata.getHeader().getId(), mergedMetadata.getHeader().getCertId());
                if (extRelation == null) {
                    throw new RuntimeException("运行时定制元数据扩展关系不存在");
                }
                extRelation.setBasicMdVersion(String.valueOf(version));
                extRelation.setExtMdVersion(newVersion);

                //保存扩展关系
                customizationServerService.saveMetadataExtRelation(extRelation);

                //保存扩展元数据
                customizationServerService.saveExtMetadata(mergedMetadata);
                customizationServerService.releaseMetadataToRt(mergedMetadata.getHeader().getId(), mergedMetadata.getHeader().getCertId());
                //元数据扩展后事件
                fireExtMdSavedEvent(mergedMetadata, extRelation, mergedChange);

                GspMdChangeset changeSet = customizationServerService.getChangeset(mergedMetadata.getHeader().getId(), mergedMetadata.getHeader().getCertId());
                if (changeSet != null) {
                    extRelation.setBasicMdVersion(String.valueOf(version));
                    changeSet.setVersion(newVersion);
                    customizationServerService.saveMdChangeSet(changeSet);
                }

                syncMetadata(mergedMetadata, dimensionExtendEntityList, manager, rootMetadata);
            }
        }
    }

    private void saveChangeSet(MetadataSyncInfo info, GspMetadata metadata, GspMetadata preversionMetadata) {
        if (info.getChangeSet() == null) {
            GspMdChangeset changeSet = customizationServerService.getChangeset(metadata.getHeader().getId(), metadata.getHeader().getCertId());
            if (changeSet != null) {
                info.setChangeSet(changeSet);
            }
        }
        if (info.getChangeSet() != null) {
            info.getChangeSet().setVersion(metadata.getVersion());
            if (preversionMetadata != null) {
                info.getChangeSet().setPreviousVersion(preversionMetadata.getVersion());
            }
            customizationServerService.saveMdChangeSet(info.getChangeSet());
        }
    }

    private void fireExtMdSavedEvent(GspMetadata metadata, GspMdExtRelation extRelation, AbstractCustomizedContent mergedChange) {
        MetadataDeployEventBroker deployEventListener = SpringBeanUtils.getBean(MetadataDeployEventBroker.class);
        ExtMdSavedArgs args = new ExtMdSavedArgs();
        DimensionExtendEntity dimensionExtendEntity = buildDimensionEntity(metadata, extRelation);
        args.setDimensionExtendEntity(dimensionExtendEntity);
        args.setMergedChange(mergedChange);
        deployEventListener.fireExtMdSavedEvent(args);
    }

    private void saveExtRelation(MetadataSyncInfo info, GspMetadata metadata) {
        if (info.getExtRelation() == null) {
            GspMdExtRelation extRelation = customizationServerService.getExtRelationByExtMdIdAndCertId(metadata.getHeader().getId(), metadata.getHeader().getCertId());
            if (extRelation == null) {
                throw new RuntimeException("运行时定制元数据扩展关系不存在");
            }
            info.setExtRelation(extRelation);
        }

        info.getExtRelation().setExtMdVersion(metadata.getVersion());
        customizationServerService.saveMetadataExtRelation(info.getExtRelation());
    }

    private DimensionExtendEntity buildDimensionEntity(GspMetadata metadata, GspMdExtRelation extRelation) {
        DimensionExtendEntity dimensionExtendEntity = new DimensionExtendEntity();
        dimensionExtendEntity.setFirstDimensionCode(extRelation.getFirstDimCode());
        dimensionExtendEntity.setFirstDimension(extRelation.getFirstDimValue());
        dimensionExtendEntity.setFirstDimensionName(extRelation.getFirstDimName());
        dimensionExtendEntity.setSecondDimensionCode(extRelation.getSecDimCode());
        dimensionExtendEntity.setSecondDimension(extRelation.getSecDimValue());
        dimensionExtendEntity.setSecondDimensionName(extRelation.getSecDimName());
        dimensionExtendEntity.setExtendMetadataEntity(metadata);
        dimensionExtendEntity.setBasicMetadataVersion(extRelation.getBasicMdVersion());
        dimensionExtendEntity.setBasicMetadataTypeStr(extRelation.getBasicMdType());
        dimensionExtendEntity.setBasicMetadataNamespace(extRelation.getBasicMdNameSpace());
        dimensionExtendEntity.setBasicMetadataId(extRelation.getBasicMdId());
        dimensionExtendEntity.setBasicMetadataCode(extRelation.getBasicMdCode());
        dimensionExtendEntity.setBasicMetadataCertId(extRelation.getExtMdCertId());

        return dimensionExtendEntity;
    }

    public void clearSyncInfo() {
        this.changeMap = new HashMap<>();
    }
}
